/*******************************************************************************

        copyright:      Copyright (c) 2004 Kris Bell. All rights reserved

        license:        BSD style: $(LICENSE)

        version:        Mar 2004: Initial release$(BR)
                        Dec 2006: Outback release$(BR)
                        Nov 2008: relocated and simplified

        authors:        Kris,
                        John Reimer,
                        Anders F Bjorklund (Darwin patches),
                        Chris Sauls (Win95 file support)

*******************************************************************************/

module tango.io.device.File;

private import tango.sys.Common;

private import tango.io.device.Device;

private import stdc = tango.stdc.stringz;

private import tango.core.Octal;

/*******************************************************************************

        platform-specific functions

*******************************************************************************/

version (Win32)
         private import Utf = tango.text.convert.Utf;
   else
      private import tango.stdc.posix.unistd;


/*******************************************************************************

        Implements a means of reading and writing a generic file. Conduits
        are the primary means of accessing external data, and File
        extends the basic pattern by providing file-specific methods to
        set the file size, seek to a specific file position and so on.

        Serial input and output is straightforward. In this example we
        copy a file directly to the console:
        ---
        // open a file for reading
        auto from = new File ("test.txt");

        // stream directly to console
        Stdout.copy (from);
        ---

        And here we copy one file to another:
        ---
        // open file for reading
        auto from = new File ("test.txt");

        // open another for writing
        auto to = new File ("copy.txt", File.WriteCreate);

        // copy file and close
        to.copy.close;
        from.close;
        ---

        You can use InputStream.load() to load a file directly into memory:
        ---
        auto file = new File ("test.txt");
        auto content = file.load;
        file.close;
        ---

        Or use a convenience static function within File:
        ---
        auto content = File.get ("test.txt");
        ---

        A more explicit version with a similar result would be:
        ---
        // open file for reading
        auto file = new File ("test.txt");

        // create an array to house the entire file
        auto content = new char [file.length];

        // read the file content. Return value is the number of bytes read
        auto bytes = file.read (content);
        file.close;
        ---

        Conversely, one may write directly to a File like so:
        ---
        // open file for writing
        auto to = new File ("text.txt", File.WriteCreate);

        // write an array of content to it
        auto bytes = to.write (content);
        ---

        There are equivalent static functions, File.set() and
        File.append(), which set or append file content respectively.

        File can happily handle random I/O. Here we use seek() to
        relocate the file pointer:
        ---
        // open a file for reading and writing
        auto file = new File ("random.bin", File.ReadWriteCreate);

        // write some data
        file.write ("testing");

        // rewind to file start
        file.seek (0);

        // read data back again
        char[10] tmp;
        auto bytes = file.read (tmp);

        file.close;
        ---

        Note that File is unbuffered by default - wrap an instance within
        tango.io.stream.Buffered for buffered I/O.

        Compile with -version=Win32SansUnicode to enable Win95 &amp; Win32s file
        support.

*******************************************************************************/

class File : Device, Device.Seek, Device.Truncate
{
        public alias Device.read  read;
        public alias Device.write write;

        /***********************************************************************

                Fits into 32 bits ...

        ***********************************************************************/

         align(1) struct Style
        {
                Access          access;                 /// Access rights.
                Open            open;                   /// How to open.
                Share           share;                  /// How to share.
                Cache           cache;                  /// How to cache.
        }

        /***********************************************************************

        ***********************************************************************/

        enum Access : ubyte     {
                                Read      = 0x01,       /// Is readable.
                                Write     = 0x02,       /// Is writable.
                                ReadWrite = 0x03,       /// Both.
                                }

        /***********************************************************************

        ***********************************************************************/

        enum Open : ubyte       {
                                Exists=0,               /// Must exist.
                                Create,                 /// Create or truncate.
                                Sedate,                 /// Create if necessary.
                                Append,                 /// Create if necessary.
                                New,                    /// Can't exist.
                                };

        /***********************************************************************

        ***********************************************************************/

        enum Share : ubyte      {
                                None=0,                 /// No sharing.
                                Read,                   /// Shared reading.
                                ReadWrite,              /// Open for anything.
                                };

        /***********************************************************************

        ***********************************************************************/

        enum Cache : ubyte      {
                                None      = 0x00,       /// Don't optimize.
                                Random    = 0x01,       /// Optimize for random.
                                Stream    = 0x02,       /// Optimize for stream.
                                WriteThru = 0x04,       /// Backing-cache flag.
                                };

        /***********************************************************************

            Read an existing file.

        ***********************************************************************/

        enum Style ReadExisting = {Access.Read, Open.Exists};

        /***********************************************************************

            Read an existing file.

        ***********************************************************************/

        enum Style ReadShared = {Access.Read, Open.Exists, Share.Read};

        /***********************************************************************

            Write on an existing file. Do not create.

        ***********************************************************************/

        enum Style WriteExisting = {Access.Write, Open.Exists};

        /***********************************************************************

                Write on a clean file. Create if necessary.

        ***********************************************************************/

        enum Style WriteCreate = {Access.Write, Open.Create};

        /***********************************************************************

                Write at the end of the file.

        ***********************************************************************/

        enum Style WriteAppending = {Access.Write, Open.Append};

        /***********************************************************************

                Read and write an existing file.

        ***********************************************************************/

        enum Style ReadWriteExisting = {Access.ReadWrite, Open.Exists};

        /***********************************************************************

                Read &amp; write on a clean file. Create if necessary.

        ***********************************************************************/

        enum Style ReadWriteCreate = {Access.ReadWrite, Open.Create};

        /***********************************************************************

                Read and Write. Use existing file if present.

        ***********************************************************************/

        enum Style ReadWriteOpen = {Access.ReadWrite, Open.Sedate};


        // the file we're working with 
        private const(char)[]  path_;

        // the style we're opened with
        private Style   style_;

        /***********************************************************************

                Create a File for use with open().

                Note that File is unbuffered by default - wrap an instance
                within tango.io.stream.Buffered for buffered I/O.

        ***********************************************************************/

        this ()
        {
        }

        /***********************************************************************

                Create a File with the provided path and style.

                Note that File is unbuffered by default - wrap an instance
                within tango.io.stream.Buffered for buffered I/O.

        ***********************************************************************/

        this (const(char[]) path, Style style = ReadExisting)
        {
                open (path, style);
        }

        /***********************************************************************

                Return the Style used for this file.

        ***********************************************************************/

        @property const Style style ()
        {
                return style_;
        }

        /***********************************************************************

                Return the path used by this file.

        ***********************************************************************/

        override immutable(char)[] toString ()
        {
                return path_.idup;
        }

        /***********************************************************************

                Convenience function to return the content of a file.
                Returns a slice of the provided output buffer, where
                that has sufficient capacity, and allocates from the
                heap where the file content is larger.

                Content size is determined via the file-system, per
                File.length, although that may be misleading for some
                *nix systems. An alternative is to use File.load which
                loads content until an Eof is encountered.

        ***********************************************************************/

        static void[] get (const(char)[] path, void[] dst = null)
        {
                scope file = new File (path);

                // allocate enough space for the entire file
                auto len = cast(size_t) file.length;
                if (dst.length < len){
                    if (dst is null){ // avoid setting the noscan attribute, one should maybe change the return type
                        dst=new ubyte[](len);
                    } else {
                        dst.length = len;
                    }
                }

                //read the content
                len = file.read (dst);
                if (len is file.Eof)
                    file.error ("File.read :: unexpected eof");

                return dst [0 .. len];
        }

        /***********************************************************************

                Convenience function to set file content and length to
                reflect the given array.

        ***********************************************************************/

        static void set (const(char)[] path, const(void)[] content)
        {
                scope file = new File (path, ReadWriteCreate);
                file.write (content);
        }

        /***********************************************************************

                Convenience function to append content to a file.

        ***********************************************************************/

        static void append (const(char)[] path, const(void)[] content)
        {
                scope file = new File (path, WriteAppending);
                file.write (content);
        }

        /***********************************************************************

                Windows-specific code

        ***********************************************************************/

        version(Win32)
        {
                /***************************************************************

                    Low level open for sub-classes that need to apply specific
                    attributes.

                    Return: False in case of failure.

                ***************************************************************/

                protected bool open (const(char)[] path, Style style, DWORD addattr)
                {
                        DWORD   attr,
                                share,
                                access,
                                create;

                        alias DWORD[] Flags;

                        static const Flags Access =
                                        [
                                        0,                      // invalid
                                        GENERIC_READ,
                                        GENERIC_WRITE,
                                        GENERIC_READ | GENERIC_WRITE,
                                        ];

                        static const Flags Create =
                                        [
                                        OPEN_EXISTING,          // must exist
                                        CREATE_ALWAYS,          // truncate always
                                        OPEN_ALWAYS,            // create if needed
                                        OPEN_ALWAYS,            // (for appending)
                                        CREATE_NEW              // can't exist
                                        ];

                        static const Flags Share =
                                        [
                                        0,
                                        FILE_SHARE_READ,
                                        FILE_SHARE_READ | FILE_SHARE_WRITE,
                                        ];

                        static const Flags Attr =
                                        [
                                        0,
                                        FILE_FLAG_RANDOM_ACCESS,
                                        FILE_FLAG_SEQUENTIAL_SCAN,
                                        0,
                                        FILE_FLAG_WRITE_THROUGH,
                                        ];

                        // remember our settings
                        assert(path);
                        path_ = path;
                        style_ = style;

                        attr   = Attr[style.cache] | addattr;
                        share  = Share[style.share];
                        create = Create[style.open];
                        access = Access[style.access];

                        version(TangoRuntime)
                        if (scheduler)
                            attr |= FILE_FLAG_OVERLAPPED;// + FILE_FLAG_NO_BUFFERING;

                        // zero terminate the path
                        char[512] zero = void;
                        auto name = stdc.toStringz (path, zero);

                        version (Win32SansUnicode)
                                 io.handle = CreateFileA (name, access, share,
                                                          null, create,
                                                          attr | FILE_ATTRIBUTE_NORMAL,
                                                          null);
                             else
                                {
                                // convert to utf16
                                wchar[512] convert = void;
                                auto wide = Utf.toString16 (name[0..path.length+1], convert);

                                // open the file
                                io.handle = CreateFileW (wide.ptr, access, share,
                                                         null, create,
                                                         attr | FILE_ATTRIBUTE_NORMAL,
                                                         null);
                                }

                        if (io.handle is INVALID_HANDLE_VALUE)
                            return false;

                        // reset extended error
                        SetLastError (ERROR_SUCCESS);

                        // move to end of file?
                        if (style.open is Open.Append)
                            *(cast(long*) &io.asynch.Offset) = -1;
                        else
                           io.track = true;

                        // monitor this handle for async I/O?
                        version(TangoRuntime)
                        if (scheduler)
                            scheduler.open (io.handle, toString);
                        return true;
                }

                /***************************************************************

                        Open a file with the provided style.

                ***************************************************************/

                void open (const(char[]) path, Style style = ReadExisting)
                {
                    if (! open (path, style, 0))
                          error();
                }

                /***************************************************************

                        Set the file size to be that of the current seek
                        position. The file must be writable for this to
                        succeed.

                ***************************************************************/

                void truncate ()
                {
                        truncate (position);
                }

                /***************************************************************

                        Set the file size to be the specified length. The
                        file must be writable for this to succeed.

                ***************************************************************/

                override void truncate (long size)
                {
                        auto s = seek (size);
                        assert (s is size);

                        // must have Generic_Write access
                        if (! SetEndOfFile (io.handle))
                              error();
                }

                /***************************************************************

                        Set the file seek position to the specified offset
                        from the given anchor.

                ***************************************************************/

                override long seek (long offset, Anchor anchor = Anchor.Begin)
                {
                        long newOffset;

                        // hack to ensure overlapped.Offset and file location
                        // are correctly in synch ...
                        if (anchor is Anchor.Current)
                            SetFilePointerEx (io.handle,
                                              *cast(LARGE_INTEGER*) &io.asynch.Offset,
                                              cast(PLARGE_INTEGER) &newOffset, 0);

                        if (! SetFilePointerEx (io.handle, *cast(LARGE_INTEGER*)
                                                &offset, cast(PLARGE_INTEGER)
                                                &newOffset, anchor))
                              error();

                        return (*cast(long*) &io.asynch.Offset) = newOffset;
                }

                /***************************************************************

                        Return the current file position.

                ***************************************************************/

                @property long position ()
                {
                        return *cast(long*) &io.asynch.Offset;
                }

                /***************************************************************

                        Return the total length of this file.

                ***************************************************************/

                @property long length ()
                {
                        long len;

                        if (! GetFileSizeEx (io.handle, cast(PLARGE_INTEGER) &len))
                              error();
                        return len;
                }

          /***************************************************************

            Instructs the OS to flush it's internal buffers to
                        the disk device.

                        NOTE: Due to OS and hardware design, data flushed
                        cannot be guaranteed to be actually on disk-platters.
                        Actual durability of data depends on write-caches,
                        barriers, presence of battery-backup, filesystem and
                        OS-support.

                ***************************************************************/

          void sync ()
          {
                         if (! FlushFileBuffers (io.handle))
                               error();
                }
        }


        /***********************************************************************

                 Unix-specific code. Note that some methods are 32bit only.

        ***********************************************************************/

        version (Posix)
        {
                /***************************************************************

                    Low level open for sub-classes that need to apply specific
                    attributes.

                    Return:
                        False in case of failure.

                ***************************************************************/

                protected bool open (const(char[]) path, Style style,
                                     int addflags, int access = octal!(666))
                {
                        alias int[] Flags;

                        enum O_LARGEFILE = 0x8000;

                        __gshared immutable Flags Access =
                                        [
                                        0,                      // invalid
                                        O_RDONLY,
                                        O_WRONLY,
                                        O_RDWR,
                                        ];
                                                
                        __gshared immutable Flags Create =
                                        [
                                        0,                      // open existing
                                        O_CREAT | O_TRUNC,      // truncate always
                                        O_CREAT,                // create if needed
                                        O_APPEND | O_CREAT,     // append
                                        O_CREAT | O_EXCL,       // can't exist
                                        ];

                        __gshared immutable short[] Locks =
                                        [
                                        F_WRLCK,                // no sharing
                                        F_RDLCK,                // shared read
                                        ];

                        // remember our settings
                        assert(path);
                        path_ = path;
                        style_ = style;

                        // zero terminate and convert to utf16
                        char[512] zero = void;
                        auto name = stdc.toStringz (path, zero);
                        auto mode = Access[style.access] | Create[style.open];

                        // always open as a large file
                        handle = posix.open (name, mode | O_LARGEFILE | addflags,
                                             access);
                        if (handle is -1)
                            return false;

                        return true;
                }

                /***************************************************************

                        Open a file with the provided style.

                        Note that files default to no-sharing. That is,
                        they are locked exclusively to the host process
                        unless otherwise stipulated. We do this in order
                        to expose the same default behaviour as Win32.

                        $(B No file locking for borked POSIX.)

                ***************************************************************/

                void open (const(char[]) path, Style style = ReadExisting)
                {
                    if (! open (path, style, 0))
                          error();
                }

                /***************************************************************

                        Set the file size to be that of the current seek
                        position. The file must be writable for this to
                        succeed.

                ***************************************************************/

                void truncate ()
                {
                        truncate (position);
                }

                /***************************************************************

                        Set the file size to be the specified length. The
                        file must be writable for this to succeed.

                ***************************************************************/

                override void truncate (long size)
                {
                        // set filesize to be current seek-position
                        if (posix.ftruncate (handle, cast(off_t) size) is -1)
                            error();
                }

                /***************************************************************

                        Set the file seek position to the specified offset
                        from the given anchor.

                ***************************************************************/

                override long seek (long offset, Anchor anchor = Anchor.Begin)
                {
                        long result = posix.lseek (handle, cast(off_t) offset, anchor);
                        if (result is -1)
                            error();
                        return result;
                }

                /***************************************************************

                        Return the current file position.

                ***************************************************************/

                @property long position ()
                {
                        return seek (0, Anchor.Current);
                }

                /***************************************************************

                        Return the total length of this file.

                ***************************************************************/

                @property long length ()
                {
                        stat_t stats = void;
                        if (posix.fstat (handle, &stats))
                            error();
                        return cast(long) stats.st_size;
                }

          /***************************************************************

            Instructs the OS to flush it's internal buffers to
                        the disk device.

                        NOTE: due to OS and hardware design, data flushed
                        cannot be guaranteed to be actually on disk-platters.
                        Actual durability of data depends on write-caches,
                        barriers, presence of battery-backup, filesystem and
                        OS-support.

                ***************************************************************/

          void sync ()
          {
                         if (fsync (handle))
                             error();
                }
        }
}


debug (File)
{
        import tango.io.Stdout;

        void main()
        {
                char[10] ff;

                auto file = new File("file.d");
                auto content = cast(char[]) file.load (file);
                assert (content.length is file.length);
                assert (file.read(ff) is file.Eof);
                assert (file.position is content.length);
                file.seek (0);
                assert (file.position is 0);
                assert (file.read(ff) is 10);
                assert (file.position is 10);
                assert (file.seek(0, file.Anchor.Current) is 10);
                assert (file.seek(0, file.Anchor.Current) is 10);
        }
}
